
#ifndef __BMANX_MAN_HEADER_FILE__
#define __BMANX_MAN_HEADER_FILE__

#include "../rsp/rsp.h"

#include "moving.h"
#include "powerup.h"
#include "bomb.h"

#include "coid/comm/rnd.h"

////////////////////////////////////////////////////////////////////////////////
class man : public moving
{
    static collisionbody _stcoll;

    rsp_dynarray<bomb,true> _bombs;

    uchar   _pwups[POWERUP::NUM_PWUP];
    
    uchar   _ntrig;                     ///< triggers available
    uchar   _nbombburn;                 ///< bombs burning now
    uchar   _nbombusable;               ///< bombs available now
    uchar   _manstate;                  enum { MS_OK=0, MS_ILL, MS_DYING };

    //uint    _itdir;                     ///< initial tied direction, used for fast kicking of tied bombs upon dir change

    ushort  _trigno;                    ///< trigger ordering counter
    ushort  _ticks;                     ///< state counter

    uint _cmdreq;

public:
    uint32 _colorpri;
    uint32 _colorsec;

    ushort _nkills;
    ushort _nsuicides;
    ushort _nwins;

    charstr _nick;

    COMMAND_CLASS(man, "id:m")

    COMMAND(8) void change_direction( uchar d )
    {
        uint k = d>>1;
        if( _pwups[POWERUP::REVERSE]>0 )
            k ^= 1;
        moving::change_direction( 1<<k, (d & fBTN_PRESS) != 0 );
    }

    COMMAND(4) void action( uchar d )
    {
        _cmdreq = (_cmdreq << 8) + d;
    }

    COMMAND(1) void set_colors( uint32 colpri, uint32 colsec, const token& nick )
    {
        _colorpri = colpri;
        _colorsec = colsec;
        _nick = nick;
    }

    COMMAND(1) void disconnect()
    {
        set_connected(0);
    }
/*
    void command( uchar cd, binstream& cmd )
    {
        uint k = cd & 0x3f;

        if( k >= BTN_EAST  &&  k <= BTN_SOUTH )
        {
            k -= BTN_EAST;
            if( _pwups[POWERUP::REVERSE]>0 )
                k ^= 1;

            change_direction( 1<<k , (cd & fBTN_PRESS) != 0 );
        }
        
        else if( k == BTN_ACT_A  ||  k == BTN_ACT_B )
            _cmdreq = (_cmdreq << 8) + cd;
        
        else if( k == BTN_COLORS )
        {
            cmd >> _colorpri >> _colorsec;
            uchar nl;
            cmd >> nl;
            cmd.xread_raw( _nick.get_buf(nl), nl );
        }
        else if( k == BTN_DISCONNECT )
        {
            set_connected(0);
        }
        else
        {
            DASSERT(0);
        }
    }
*/

    void state_save( frame_command_buffer& fcb ) const
    {
        MARK_OUT('m');
        moving::state_save(fcb);
        fcb.setb( _pwups, sizeof(_pwups) );
        fcb << _nick;
        _bombs.state_save(fcb);
        MARK_OUT('m'|0x80);
    }

    void state_load( frame_command_reader& fcr )
    {
        MARK_IN('m');
        moving::state_load(fcr);
        fcr.getb( _pwups, (uchar*)&_nick - (uchar*)_pwups );
        fcr >> _nick;
        _bombs.state_load( fcr, get_tile() );
        MARK_IN('m'|0x80);
    }


    uint inc_wins()
    {
        ++_nwins;
        return _nwins;
    }

    enum {
        MAXBOMBS                    = 16,

        TIME_TO_DIE                 = 1000,
        TIME_TO_BE_ILL              = 16000,
        TIME_TO_VOMIT               = 800,

        BTN_EAST                    = 1,
        BTN_WEST                    = 2,
        BTN_NORTH                   = 3,
        BTN_SOUTH                   = 4,

        BTN_ACT_A                   = 5,
        BTN_ACT_B                   = 6,

        BTN_COLORS                  = 7,
        BTN_DISCONNECT              = 8,

        fBTN_PRESS                  = 0x01,

        TRIGGER_LATENCY             = 2,
    };


    man() : moving(true,&_stcoll)
    {
        reset(false,0);

        _colorpri = _colorsec = UMAX;
    }

    void reset( bool round, const uchar* initpwups )
    {
        if(!round)
        {
            _nwins = _nkills = _nsuicides = 0;
            _nick.reset();
        }

        moving::reset();

        _ntrig = _nbombburn = 0;
        _manstate = MS_OK;
        _ticks = 0;
        _trigno = 0;
        _cmdreq = 0;

        if(initpwups)
            xmemcpy( _pwups, initpwups, POWERUP::NUM_PWUP*sizeof(POWERUP) );
        else
            load_default_pwups();

        _nbombusable = _pwups[POWERUP::BOMB];

        _bombs.init(MAXBOMBS);
        for( uint i=0; i<MAXBOMBS; ++i )
            _bombs[i]->reset();

        set_speed( _pwups[POWERUP::SKATE] - _pwups[POWERUP::BRAKE],
            _pwups[POWERUP::VOLANT] - _pwups[POWERUP::CRAWL] );
        touch();
    }



    void load_default_pwups()
    {
        memset( _pwups, 0, POWERUP::NUM_PWUP*sizeof(POWERUP) );

        _pwups[POWERUP::BOMB] = 1;
        _pwups[POWERUP::FLAME] = 2;
    }

/*
    uint get_initial_tied_direction() const     { return _itdir; }
    void set_initial_tied_direction()
    {
        _itdir = get_direction_or_stop();
    }
    void set_initial_tied_direction_used()
    {
        _itdir = UMAX;
    }
    void set_initial_tied_direction_clear()
    {
        _itdir = 0;
    }
*/
    uint get_powerup( uint n ) const            { return _pwups[n]; }

    uint get_colorpri() const                   { return _colorpri; }
    uint get_colorsec() const                   { return _colorsec; }


    void remove_trigger( dynarray<POWERUP>& pwout )
    {
        if( _pwups[POWERUP::TRIGGER] ) {
            pwout.pushn( POWERUP( POWERUP::TRIGGER ), _pwups[POWERUP::TRIGGER] );
            _pwups[POWERUP::TRIGGER] = 0;
        }

        _ntrig = 0;
        for( uint i=0; i<MAXBOMBS; ++i )
        {
            if( _bombs[i]->in_world() )
                _bombs[i]->clr_trigger();
        }
    }

    bool fire_trigger()
    {
        uint b=UMAX, bid=UMAX;
        for( uint i=0; i<MAXBOMBS; ++i )
        {
            if( _bombs[i]->in_world()  &&  _bombs[i]->is_trigger_unfired()  &&  _bombs[i]->get_seqid()<bid )
                b = i, bid = _bombs[i]->get_seqid();
        }

        if( b != UMAX )
            _bombs[b]->prepare_detonation( 0, TRIGGER_LATENCY );
        return b != UMAX;
    }



    void fetch_powerups( dynarray<POWERUP>& pwout )
    {
        for( uint i=POWERUP::NONE+1; i<POWERUP::NUM_PWUP; ++i )
        {
            uchar n = _pwups[i];
            if( n > 0 )
            {
                if( i == POWERUP::BOMB || i == POWERUP::FLAME || i == POWERUP::SKATE )
                    n >>= 1;

                pwout.pushn( POWERUP(i), n );
                _pwups[i] = 0;
            }
        }
    }

    void add_powerups( const dynarray<POWERUP>& pwin, dynarray<POWERUP>& pwout )
    {
        for( uint i=0; i<pwin.size(); ++i )
        {
            POWERUP pw = pwin[i];
            add_powerup( pw, 0, pwout );
        }
    }

    void add_powerup( POWERUP pw, uint ticks, dynarray<POWERUP>& pwout )
    {
        switch( pw._type )
        {
        case POWERUP::FLAME:
            /*if( _pwups[POWERUP::GOLDFLAME] ) {
                //you have goldflame already you greedy thing
                *pwout.add() = POWERUP( POWERUP::GOLDFLAME );
                _pwups[POWERUP::GOLDFLAME] = 0;
            }*/
            ++_pwups[POWERUP::FLAME];
            break;

        case POWERUP::GRAB:
            //kick off punch if set
            if( _pwups[POWERUP::SPOOGE] ) {
                pwout.pushn( POWERUP( POWERUP::SPOOGE ), _pwups[POWERUP::SPOOGE] );
                _pwups[POWERUP::SPOOGE] = 0;
            }

            _pwups[pw._type] = 1;
            break;

        case POWERUP::SPOOGE:
            //kick off punch if set
            if( _pwups[POWERUP::GRAB] ) {
                pwout.pushn( POWERUP( POWERUP::GRAB ), _pwups[POWERUP::GRAB] );
                _pwups[POWERUP::GRAB] = 0;
            }

            _pwups[pw._type] = 1;
            break;

        case POWERUP::GOLDFLAME:
        case POWERUP::KICK:
            _pwups[pw._type] = 1;
            break;

        //punch bomb(1), punch man(2)
        case POWERUP::PUNCH:
            //kick off trigger if set
            remove_trigger(pwout);

            ++_pwups[POWERUP::PUNCH];
            break;


        case POWERUP::JELLY:
            //kick off trigger if set
            remove_trigger(pwout);

            _pwups[POWERUP::JELLY] = 1;
            break;

        case POWERUP::TRIGGER:
            //kick off punch if set
            if( _pwups[POWERUP::PUNCH] ) {
                pwout.pushn( POWERUP( POWERUP::PUNCH ), _pwups[POWERUP::PUNCH] );
                _pwups[POWERUP::PUNCH] = 0;
            }
            //kick off jelly if set
            if( _pwups[POWERUP::JELLY] ) {
                pwout.pushn( POWERUP( POWERUP::JELLY ), _pwups[POWERUP::JELLY] );
                _pwups[POWERUP::JELLY] = 0;
            }
            if( ++_pwups[POWERUP::TRIGGER] == 1 )
                _trigno = 0;
            _ntrig += _pwups[POWERUP::BOMB];
            break;

        //unlimited:
        case POWERUP::SKATE:
        case POWERUP::BRAKE:
            ++_pwups[pw._type];
            compute_speed();
            break;

        case POWERUP::BOMB:
            ++_pwups[pw._type];
            ++_nbombusable;
            if( _pwups[POWERUP::TRIGGER] )
                ++_ntrig;
            break;

        case POWERUP::THROWFLAME:
            if( _pwups[pw._type] )
                break;
            if( _pwups[POWERUP::FLAME] > 1 ) {
                pwout.pushn( POWERUP::FLAME, _pwups[POWERUP::FLAME]-1 );
                _pwups[POWERUP::FLAME] = 1;
            }
            if( _pwups[POWERUP::GOLDFLAME] > 0 ) {
                pwout.pushn( POWERUP::GOLDFLAME, _pwups[POWERUP::FLAME] );
                _pwups[POWERUP::GOLDFLAME] = 0;
            }
            _manstate = MS_ILL;
            if(!_ticks)
                _ticks = MSEC_TO_TICKS(TIME_TO_VOMIT);
            ++_pwups[pw._type];
            break;

        case POWERUP::THROWSKATE:
            if( _pwups[pw._type] )
                break;
            if( _pwups[POWERUP::SKATE] > 1 ) {
                pwout.pushn( POWERUP::SKATE, _pwups[POWERUP::SKATE]-1 );
                _pwups[POWERUP::SKATE] = 1;
            }
            if( _pwups[POWERUP::BRAKE] > 0 ) {
                pwout.pushn( POWERUP::BRAKE, _pwups[POWERUP::BRAKE] );
                _pwups[POWERUP::BRAKE] = 0;
            }
            _manstate = MS_ILL;
            if(!_ticks)
                _ticks = MSEC_TO_TICKS(TIME_TO_VOMIT);
            ++_pwups[pw._type];
            compute_speed();
            break;

        case POWERUP::THROWBOMB:
            if( _pwups[pw._type] )
                break;
            if( _pwups[POWERUP::BOMB] > 1 ) {
                pwout.pushn( POWERUP::BOMB, _pwups[POWERUP::BOMB]-1 );
                _pwups[POWERUP::BOMB] = 1;
            }
            _manstate = MS_ILL;
            if(!_ticks)
                _ticks = MSEC_TO_TICKS(TIME_TO_VOMIT);
            ++_pwups[pw._type];
            break;

        case POWERUP::THROWSPECIAL:
            if( _pwups[pw._type] )
                break;
            {
                //KICK, GRAB, PUNCH, JELLY, TRIGGER, SKATE, SPOOGE, BRAKE
                uint prnd = (rsp_controller::current->get_current_stateid() + 1) * rnd_int::get_multiplier();
                for( uint k=POWERUP::KICK; k<=POWERUP::BRAKE; ++k,prnd>>=1 )
                {
                    if( (prnd&1) && _pwups[k]>0 )
                    {
                        pwout.pushn( k, _pwups[k] );
                        _pwups[k] = 0;
                    }
                }
                _manstate = MS_ILL;
                if(!_ticks)
                    _ticks = MSEC_TO_TICKS(TIME_TO_VOMIT);
                ++_pwups[pw._type];
                break;
            }

        case POWERUP::COMBINED:
            {
                //3 various types of sickness
                uint prnd = rsp_controller::current->get_current_stateid();

                for( uint j=0; j<3; ++j )
                {
                    prnd = (prnd + 1) * rnd_int::get_multiplier();

                    uint k = prnd % (POWERUP::COMBINED-POWERUP::FIRST_NEG);
                    k += POWERUP::FIRST_NEG;

                    add_powerup( k, 0, pwout );
                }

                break;
            }

        case POWERUP::CRAWL:
        case POWERUP::VOLANT:
            if( _pwups[pw._type] )
                break;

            _manstate = MS_ILL;
            if(!ticks)
                ticks = MSEC_TO_TICKS(TIME_TO_BE_ILL);
            _ticks = ticks;
            ++_pwups[pw._type];

            compute_speed();
        
            break;

        case POWERUP::NOBOMBS:
        case POWERUP::EJECT:
        case POWERUP::FASTDETONATE:
        case POWERUP::REVERSE:
        case POWERUP::SHORTFLAME:
            if( _pwups[pw._type] )
                break;

            _manstate = MS_ILL;
            if(!ticks)
                ticks = MSEC_TO_TICKS(TIME_TO_BE_ILL);
            _ticks = ticks;
            ++_pwups[pw._type];

            if( pw._type == POWERUP::REVERSE )
            {
                uint d = get_direction();
                if( d & (dir::fEAST|dir::fNORTH) )
                    d <<= 1;
                else
                    d >>= 1;
                if(d)
                    moving::change_direction( d, true );
            }
            break;
        }
    }


    
    void set_color( uint c )
    {
        set_man( c, 0xff );
        for( uint i=0; i<_bombs.size(); ++i )
            _bombs[i]->set_man( c, i );
    }

    void compute_speed()
    {
        set_speed( _pwups[POWERUP::SKATE] - _pwups[POWERUP::BRAKE],
            _pwups[POWERUP::VOLANT] - _pwups[POWERUP::CRAWL] );
    }

    rsp_tile* frame()
    {
        if( _ticks > 0 )  { --_ticks;  return 0; }
        if( _manstate == MS_OK )  return 0;

        if( _manstate == MS_DYING )
        {
            deactivate();
            _manstate = MS_OK;
        }
        else if( _manstate == MS_ILL )
        {
            //illness ending, restore the state
            if( _pwups[POWERUP::CRAWL] || _pwups[POWERUP::VOLANT] )
                set_speed( _pwups[POWERUP::SKATE] - _pwups[POWERUP::BRAKE], 0 );

            if( _pwups[POWERUP::REVERSE] )
            {
                uint d = get_direction();
                if( d & (dir::fEAST|dir::fNORTH) )
                    d <<= 1;
                else
                    d >>= 1;
                if(d)
                    set_orientation(d);
            }

            for( uint i=POWERUP::FIRST_NEG; i<=POWERUP::LAST_NEG; ++i )
                _pwups[i] = 0;

            _manstate = MS_OK;
        }

        return 0;
    }

    uint get_context( uint cluster ) const
    {
        return 0;
    }


    uint pop_cmdreq()
    {
        uint cmd = _cmdreq;
        _cmdreq = 0;
        return cmd;
    }


    static void send_maninfo( binstream& bin, uint32 colpri, uint32 colsec, token nick )
    {
        nick.truncate(255);

        bin << (uchar)man::BTN_COLORS << colpri << colsec;
        bin << (uchar)nick.len();
        bin.xwrite_raw( nick.ptr(), nick.len() );
    }

    static void write_disconnect( binstream& bin )
    {
        bin << (uchar)BTN_DISCONNECT;
    }



    uint get_usable_bombs() const   { return _nbombusable; }

    bomb* get_bomb()
    {
        if( _nbombusable == 0  ||  (_pwups[POWERUP::NOBOMBS] > 0 && !get_handsup()) )
            return 0;

        uint n = _pwups[POWERUP::BOMB] + _nbombburn;
        if( n>MAXBOMBS )  n = MAXBOMBS;

        for( uint i=0; i<MAXBOMBS && i<n; ++i )
        {
            if( !_bombs[i]->in_world() )
            {
                _bombs[i]->set_jelly( get_powerup(POWERUP::JELLY) > 0 );
                
                if(_ntrig)
                    --_ntrig, _bombs[i]->set_trigger( (uchar)_trigno++ );
                else
                    _bombs[i]->clr_trigger();

                int fl;
                if( get_powerup(POWERUP::SHORTFLAME) )
                    fl = 2;
                else if( get_powerup(POWERUP::GOLDFLAME) )
                    fl = 127;
                else
                    fl = get_powerup(POWERUP::FLAME) + 1;

                if(fl>127)
                    fl = 127;
                _bombs[i]->set_flame(fl);

                //_itdir = get_direction_or_stop();   //mark the initial tied direction

                --_nbombusable;
                return _bombs[i];
            }
            else if( _bombs[i]->in_fire() )
                continue;
        }
        return 0;
    }


    ushort get_ticks() const                { return _ticks; }

    uint trigger_count() const              { return _ntrig; }

    void burn()
    {
        _ticks = MSEC_TO_TICKS(TIME_TO_DIE);
        _manstate = MS_DYING;
        set_burning();
        stop();
    }

    bool is_dying() const                   { return _manstate == MS_DYING; }
    bool is_ill() const                     { return _manstate == MS_ILL; }

    void bomb_back( bool trigger )          { ++_nbombusable;  if(trigger) ++_ntrig; }
    void bomb_burning()                     { ++_nbombusable; ++_nbombburn; }
    void bomb_went_off()                    { --_nbombburn; }

    //rsp_dynarray<bomb,true>& get_bombs()    { return _bombs; }
    const rsp_dynarray<bomb,true>& get_bombs() const    { return _bombs; }

    ////////////////////////////////////////////////////////////////////////////////
/*
    void rsp_attach( rsp_s_connector& ctrl )
    {
        ctrl.view_add(this);

        for( uint i=0; i<MAXBOMBS; ++i )
        {
            _bombs[i]->rsp_attach(ctrl);
        }
    }

    void rsp_detach( rsp_s_connector& ctrl )
    {
        ctrl.view_del(this);

        for( uint i=0; i<MAXBOMBS; ++i )
        {
            _bombs[i]->rsp_detach(ctrl);
        }
    }
*/
    //RSP_CLASS_NAME("m",man)
    ////////////////////////////////////////////////////////////////////////////////
    
    friend binstream& operator << (binstream& bin, const man& m)
    {
        bin << m._nick << m._nwins << m._nkills << m._nsuicides;
        return bin;
    }

    friend binstream& operator >> (binstream& bin, man& m)
    {
        bin >> m._nick >> m._nwins >> m._nkills >> m._nsuicides;
        return bin;
    }

    friend metastream& operator << (metastream& m, const man& a)
    {
        MSTRUCT_OPEN(m, "player");
        MM(m, "nick", a._nick);
        MM(m, "wins", a._nwins);
        MM(m, "kills", a._nkills);
        MM(m, "suicides", a._nsuicides);
        MSTRUCT_CLOSE(m);
    }
};


#endif //__BMANX_MAN_HEADER_FILE__
